------------Picture Perfect------------
A 4am crack                  2017-11-19
---------------------------------------

Name: Picture Perfect
Genre: graphics
Year: 1989
Publisher: Mindplay, Inc.
Platform: Apple ][+ or later
Media: single-sided 5.25-inch floppy
OS: DOS 3.3
Previous cracks: none

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  read error

Locksmith Fast Disk Backup
  unable to read track $0F
  copy boots DOS, shows a title screen,
  then hangs with the drive motor on

EDD 4 bit copy (no sync, no count)
  no errors, but copy boots DOS, shows
  a title screen, then displays
  "DISK ERROR 35W" and hangs

Copy ][+ nibble editor
  track $0F is almost entirely sync
  bytes, with the occasional $D5 nibble
  (almost certainly a protection track)

Disk Fixer
  entire disk is standard except T0F
  T00,S00 -> DOS 3.3 bootloader
  T01,S09 -> startup program is "HELLO"
  standard DOS 3.3 disk catalog on T11

Why didn't any of my copies work?
  specially formatted nibble sequence
  on track $0F, designed to fool even
  the best bit copiers

Next steps:

  1. Find the protection check that is
     reading track $0F
  2. Disable it
  3. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
         In Which We Get Lucky


One thing that all protection checks
have in common is they need to access
the disk drive. Since most protection
checks exploit edge cases of how bits
are stored on disk, they need to use
the lowest level access methods to
manipulate those bits manually.

The lowest level way to "read" a disk
is the data latch softswitch address in
the $C0xx range. For slot 6, it's
$C0EC, but to allow disks to boot from
any slot, developers usually use code
like this:

  LDX <slot number x 16>
  LDA $C08C,X

There's nothing that says you have to
use the X-register as the index or the
accumulator as the load register. But
most disks do, out of convention I
suppose (or fear of messing up such
low-level code in subtle ways).

Also, since developers don't actually
want people finding their protection-
related code, they may try to encrypt
it or obfuscate it on disk, in memory,
or both. But eventually, the code must
exist and the code must run, and it
must run on my machine, and I have the
final say on what my machine does or
does not do.

But sometimes you get lucky.

Turning to my trusty Disk Fixer sector
editor, I search the non-working copy
for "BD 8C C0", which is the opcode
sequence for "LDA $C08C,X".

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      ["BD 8C C0"]

                 --v--

------------- DISK SEARCH -------------

$00/$02-$75   $00/$02-$8B   $00/$02-$B4
$00/$02-$E1   $00/$02-$EB   $00/$02-$F6
$00/$03-$2F   $00/$03-$39   $00/$03-$4F
$00/$03-$59   $00/$03-$64   $00/$03-$71
$00/$03-$79   $00/$03-$8B   $00/$03-$95
$00/$06-$A4   $00/$06-$C0   $00/$07-$27
$00/$07-$37   $00/$07-$3C   $06/$01-$D5
$08/$08-$3A   $08/$08-$4A   $08/$08-$65
$08/$08-$76   $08/$08-$80   $08/$08-$8F

                 --^--

The matches on track $00 are part of
DOS 3.3, i.e. not suspicious. The one
on track $06 seems harmless. But the
cluster of matches on track $08 are
highly suspect.

                   ~

               Chapter 2
      In Which We Find Ourselves
          In Enemy Territory


The protection routine appears to start
at offset $04.

                 --v--

T08,S08
----------- DISASSEMBLY MODE ----------
; save some things on the stack and get
; the boot slot (x16) from zero page
0004:08             PHP
0005:A6 2B          LDX   $2B
0007:A5 FD          LDA   $FD
0009:48             PHA
000A:A5 FE          LDA   $FE
000C:48             PHA

; get RWTS parameter table address
000D:20 E3 03       JSR   $03E3
0010:84 FD          STY   $FD
0012:85 FE          STA   $FE

; track $0F -- the mystery track!
0014:A0 04          LDY   #$04
0016:A9 0F          LDA   #$0F
0018:91 FD          STA   ($FD),Y

; RWTS command = seek
001A:A0 0C          LDY   #$0C
001C:A9 00          LDA   #$00
001E:91 FD          STA   ($FD),Y

; slow down IIgs (has no effect on
; other machines)
0020:AD 36 C0       LDA   $C036
0023:29 7F          AND   #$7F
0025:8D 36 C0       STA   $C036

; disable last line of RWTS that turns
; off the drive motor
0028:A9 60          LDA   #$60
002A:8D 4D BE       STA   $BE4D

; call the RWTS to execute the seek to
; track $15
002D:20 E3 03       JSR   $03E3
0030:20 D9 03       JSR   $03D9

; restore the RWTS code (but the drive
; motor is still on)
0033:A9 BD          LDA   #$BD
0035:8D 4D BE       STA   $BE4D

; if the seek failed for some reason,
; exit
0038:B0 65          BCS   $009F

; find $D5 nibble
003A:BD 8C C0       LDA   $C08C,X
003D:10 FB          BPL   $003A
003F:48             PHA
0040:68             PLA
0041:C9 D5          CMP   #$D5
0043:D0 F5          BNE   $003A

; initialize a checksum
0045:A0 00          LDY   #$00
0047:8C CF 93       STY   $93CF

; count number of $F7 nibbles before
; another $D5 nibble
004A:BD 8C C0       LDA   $C08C,X
004D:10 FB          BPL   $004A
004F:C9 D5          CMP   #$D5
0051:F0 0F          BEQ   $0062
0053:C9 F7          CMP   #$F7
0055:D0 01          BNE   $0058
0057:C8             INY

; the sum of the nibbles themselves
; constitutes the checksum
0058:18             CLC
0059:6D CF 93       ADC   $93CF
005C:8D CF 93       STA   $93CF
005F:4C 46 93       JMP   $9346
0062:98             TYA
0063:F0 E0          BEQ   $0045

; skip $FF nibbles
0065:BD 8C C0       LDA   $C08C,X
0068:10 FB          BPL   $0065
006A:48             PHA
006B:68             PLA
006C:C9 FF          CMP   #$FF
006E:F0 F5          BEQ   $0065

; if next nibble is $D5, fail
0070:C9 D5          CMP   #$D5
0072:F0 36          BEQ   $00AA

; skip several more nibbles
0074:A0 05          LDY   #$05
0076:BD 8C C0       LDA   $C08C,X
0079:10 FB          BPL   $0076
007B:48             PHA
007C:68             PLA
007D:88             DEY
007E:D0 F6          BNE   $0076

; skip $FF nibbles
0080:BD 8C C0       LDA   $C08C,X
0083:10 FB          BPL   $0080
0085:48             PHA
0086:68             PLA
0087:C9 FF          CMP   #$FF
0089:F0 F5          BEQ   $0080

; if next nibble is not $D5, fail
008B:C9 D5          CMP   #$D5
008D:D0 1B          BNE   $00AA

; skip $FF nibbles
008F:BD 8C C0       LDA   $C08C,X
0092:10 FB          BPL   $008F
0094:C9 FF          CMP   #$FF
0096:D0 12          BNE   $00AA

; verify checksum, branch on failure
0098:AD CF 93       LDA   $93CF
009B:C9 10          CMP   #$10
009D:D0 0B          BNE   $00AA

; success path falls through to here --
; turn off drive motor and restore the
; state of things before returning to
; the caller
009F:BD 88 C0       LDA   $C088,X
00A2:68             PLA
00A3:85 FE          STA   $FE
00A5:68             PLA
00A6:85 FD          STA   $FD
00A8:28             PLP
00A9:60             RTS

; all failures lead here --
; wipe memory
00AA:A2 80          LDX   #$80
00AC:A9 A0          LDA   #$A0
00AE:99 FF 08       STA   $08FF,Y
00B1:C8             INY
00B2:D0 FA          BNE   $00AE
00B4:EE AC 93       INC   $93AC
00B7:CA             DEX
00B8:D0 F4          BNE   $00AE
00BA:AD 8A C0       LDA   $C08A

; display encrypted error message
; "DISK ERROR 35W"
00BD:20 2F FB       JSR   $FB2F
00C0:20 58 FC       JSR   $FC58
00C3:A0 0E          LDY   #$0E
00C5:B9 D0 93       LDA   $93D0,Y
00C8:49 BB          EOR   #$BB
00CA:99 B4 05       STA   $05B4,Y
00CD:88             DEY
00CE:10 F5          BPL   $00C5
00D0:78             SEI

; hang forever
00D1:30 FE          BMI   $00D1

(This is the behavior I saw on my non-
working EDD bit copy.)

There are no side effects. Replacing
the first byte with an "RTS" will
bypass the entire thing.

T08,S08,$04: 08 -> 60

]PR#6
...works...

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 1529
------------------EOF------------------
